﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Xml;

namespace Fadd.Logging.Targets
{
    /// <summary>
    /// Console logging
    /// </summary>
    /// <remarks>
    /// An internal queue (and .Net thread pool) is used since writing to the console
    /// can take some time and therefore delay program execution.
    /// </remarks>
    public class ConsoleTarget : ITarget
    {
        private readonly Queue<LogEntry> _queue = new Queue<LogEntry>();
        private bool _isWriting;

		/// <summary>
		/// Initializes a new instance of the <see cref="ConsoleTarget"/> class.
		/// </summary>
		/// <param name="formatter">Formatter used to format log entries.</param>
		/// <exception cref="ArgumentNullException"><c>formatter</c> is <c>null</c>.</exception>
        public ConsoleTarget(IFormatter formatter)
        {
			if (formatter == null)
				throw new ArgumentNullException("formatter");

            Formatter = formatter;
        }

    	/// <summary>
    	/// Load configuration.
    	/// </summary>
    	/// <param name="configuration">Configuration to load.</param>
    	/// <exception cref="InvalidOperationException">If configuration has already been loaded.</exception>
    	/// <exception cref="ArgumentNullException"><c>configuration</c> is null.</exception>
		/// <exception cref="ConfigurationErrorsException">Configuration could not be loaded properly.</exception>
		public void LoadConfiguration(TargetConfiguration configuration)
    	{
			if (configuration == null)
				throw new ArgumentNullException("configuration");

    		Name = configuration.Name;
    		foreach (XmlNode node in configuration.ChildConfiguration)
    		{
				try
				{
					if (node.ChildNodes.Count != 1)
						throw new ConfigurationErrorsException("Expected ConsoleTarget, child element " + node.Name + " to have a single value.");

					object value = Enum.Parse(typeof(ConsoleColor), node.FirstChild.Value, true);
					GetType().GetProperty(node.Name).SetValue(this, value, null);
				}
				catch (FormatException err)
				{
					throw new ConfigurationErrorsException("Failed to parse color value '" + node.FirstChild.Value + "'.", err);
				}
    		}
		}

    	/// <summary>
    	/// Gets or sets target name.
    	/// </summary>
    	public string Name { get; private set; }

    	/// <summary>
    	/// Gets or sets formatter
    	/// </summary>
    	public IFormatter Formatter { get; private set; }

    	/// <summary>
        /// Write a log entry in the target.
        /// </summary>
        /// <param name="logEntry">entry to write</param>
        public void Write(LogEntry logEntry)
        {
            lock (_queue)
            {
                _queue.Enqueue(logEntry);
                if (_isWriting) return;

                System.Threading.ThreadPool.QueueUserWorkItem(WriteLogEntry);
                _isWriting = true;
            }
        }

        private void WriteLogEntry(object state)
        {
            LogEntry entry;
            lock (_queue)
                entry = _queue.Dequeue();

            string msg = Formatter.Format(entry);
            ConsoleColor color = Console.ForegroundColor;
            Console.ForegroundColor = GetColor(entry.Level);
            Console.WriteLine(msg);
            Console.ForegroundColor = color;

            lock (_queue)
            {
                if (_queue.Count == 0)
                {
                    _isWriting = false;
                    return;
                }
            }

            WriteLogEntry(null);
        }

        private ConsoleColor GetColor(LogLevel level)
        {
            switch (level)
            {
                case LogLevel.Trace:
                    return TraceColor;
                case LogLevel.Debug:
                    return DebugColor;
                case LogLevel.Info:
                    return InfoColor;
                case LogLevel.Warning:
                    return WarningColor;
                case LogLevel.Error:
                    return ErrorColor;
                case LogLevel.Fatal:
                    return FatalColor;
                default:
                    return ConsoleColor.Gray;
            }
        }

        /// <summary>
        /// Gets or sets color on warning messages
        /// </summary>
        public ConsoleColor WarningColor { get; set; }

        /// <summary>
        /// Gets or sets color on error messages
        /// </summary>
        public ConsoleColor ErrorColor { get; set; }

        /// <summary>
        /// Gets or sets color on debug messages
        /// </summary>
        public ConsoleColor DebugColor { get; set; }

        /// <summary>
        /// Gets or sets color on trace messages
        /// </summary>
        public ConsoleColor TraceColor { get; set; }

        /// <summary>
        /// Gets or sets color on information messages
        /// </summary>
        public ConsoleColor InfoColor { get; set; }

        /// <summary>
        /// Gets or sets color on fatal messages
        /// </summary>
        public ConsoleColor FatalColor { get; set; }

    }
}